/*
    Copyright 2009 Semantic Discovery, Inc. (www.semanticdiscovery.com)

    This file is part of the Semantic Discovery Toolkit.

    The Semantic Discovery Toolkit is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The Semantic Discovery Toolkit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with The Semantic Discovery Toolkit.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;

namespace SemanticDiscovery.Util.Range
{
  /// <summary>
  /// NUnit Tests for the IntegerRange class.
  /// </summary>
  /// <author>Spence Koehler</author>
  [TestFixture]
  public class TestIntegerRange {

    private void verify(IntegerRange range, 
                        int[] low, bool[] includeLow, 
                        int[] high, bool[] includeHigh, 
                        string asString, int? size) 
    {
      IList<AbstractNumericRange.SimpleRange> simpleRanges = range.GetSimpleRanges();

      Assert.AreEqual(low.Length, simpleRanges.Count);

      int index = 0;
      foreach (AbstractNumericRange.SimpleRange simpleRange in simpleRanges) {
        verify(simpleRange, 
               low[index], includeLow[index], 
               high[index], includeHigh[index]);
        ++index;
      }

      Assert.AreEqual(asString, range.AsString());
      Assert.AreEqual(size, range.Count);
    }

    private void verify(AbstractNumericRange.SimpleRange range, 
                        int low, bool includeLow, 
                        int high, bool includeHigh) 
    {
      int rangeLow = range.LowAsInt;
      int rangeHigh = range.HighAsInt;

      Assert.AreEqual(low, rangeLow);
      Assert.AreEqual(high, rangeHigh);

      Assert.AreEqual(includeLow, range.IncludesLow);
      Assert.AreEqual(includeHigh, range.IncludesHigh);

      Assert.AreEqual(includeLow, range.Includes(low));
      Assert.AreEqual(includeLow, range.Includes(low.ToString()));
      Assert.AreEqual(includeLow, range.Includes(Convert.ToDouble(low)));

      Assert.AreEqual(includeHigh, range.Includes(high));
      Assert.AreEqual(includeHigh, range.Includes(high.ToString()));
      Assert.AreEqual(includeHigh, range.Includes(Convert.ToDouble(high)));

      for (int i = low + 1; i < high && i < (low + 100); ++i) {
        Assert.IsTrue(range.Includes(i));
        Assert.IsTrue(range.Includes(i.ToString()));
        Assert.IsTrue(range.Includes(Convert.ToDouble(i)));
      }

      for (int i = high - 100; i > low + 1 && i < high; ++i) {
        Assert.IsTrue(range.Includes(i));
        Assert.IsTrue(range.Includes(i.ToString()));
        Assert.IsTrue(range.Includes(Convert.ToDouble(i)));
      }

      if (low != int.MinValue) {
        for (int i = low - 5; i < low; ++i) {
          Assert.IsFalse(range.Includes(i));
          Assert.IsFalse(range.Includes(i.ToString()));
          Assert.IsFalse(range.Includes(Convert.ToDouble(i)));
        }
      }

      if (high != int.MaxValue) {
        for (int i = high + 1; i < high + 5; ++i) {
          Assert.IsFalse(range.Includes(i));
          Assert.IsFalse(range.Includes(i.ToString()));
          Assert.IsFalse(range.Includes(Convert.ToDouble(i)));
        }
      }
    }

    [TestCase]
    public void TestIncrementalConstruction() {
      IntegerRange range = new IntegerRange();

      Assert.AreEqual(0, range.Low);
      Assert.AreEqual(0, range.High);

      range.Add(3, true, 7, true);
      Assert.AreEqual(3, range.Low);
      Assert.AreEqual(7, range.High);

      range.Add(5, true, 8, true);
      Assert.AreEqual(3, range.Low);
      Assert.AreEqual(8, range.High);
    }

    [TestCase]
    public void TestUnspecifiedBoundsString() {
      IntegerRange range = new IntegerRange("3-8");
      Assert.AreEqual(3, range.Low);
      Assert.AreEqual(8, range.High);
    }

    [TestCase]
    public void TestSingleValue() {
      IntegerRange range = null;

      range = new IntegerRange(5);
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("5");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("(5)");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("[5]");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("(5]");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("[5)");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("[ 5   ]");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange("5-5");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);

      range = new IntegerRange(" 5 - 5 ");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{5}, new bool[]{true}, "5", 1);
    }

    [TestCase]
    public void TestForwardRange() {
      IntegerRange range = null;

      range = new IntegerRange(3, true, 7, true);
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{true}, "3-7", 5);

      range = new IntegerRange(3, false, 7, false);
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{false}, "(3-7)", 3);

      range = new IntegerRange(3, false, 7, true);
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);

      range = new IntegerRange(3, true, 7, false);
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{false}, "[3-7)", 4);


      range = new IntegerRange("3-7");
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{true}, "3-7", 5);

      range = new IntegerRange("[3-7]");
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{true}, "3-7", 5);

      range = new IntegerRange("(3-7)");
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{false}, "(3-7)", 3);

      range = new IntegerRange("(3-7]");
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);

      range = new IntegerRange("[3-7)");
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{false}, "[3-7)", 4);

      range = new IntegerRange("(2-3)");
      verify(range, new int[]{2}, new bool[]{false}, new int[]{3}, new bool[]{false}, "(2-3)", null);
    }

    [TestCase]
    public void TestBackwardRange() {
      IntegerRange range = null;

      range = new IntegerRange(7, true, 3, true);
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{true}, "3-7", 5);

      range = new IntegerRange(7, false, 3, false);
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{false}, "(3-7)", 3);

      range = new IntegerRange(7, false, 3, true);
      verify(range, new int[]{3}, new bool[]{true}, new int[]{7}, new bool[]{false}, "[3-7)", 4);

      range = new IntegerRange(7, true, 3, false);
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);

      range = new IntegerRange("(3-7]");
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);

      range = new IntegerRange("[7-3)");
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);

      range = new IntegerRange(" ( 3 - 7 ] ");
      verify(range, new int[]{3}, new bool[]{false}, new int[]{7}, new bool[]{true}, "(3-7]", 4);
    }

    [TestCase]
    public void TestToleranceRange() {
      IntegerRange range = null;

      range = new IntegerRange(3, 7, true, true);
      verify(range, new int[]{-4}, new bool[]{true}, new int[]{10}, new bool[]{true}, "-4-10", 15);

      range = new IntegerRange(3, 7, false, false);
      verify(range, new int[]{-4}, new bool[]{false}, new int[]{10}, new bool[]{false}, "(-4-10)", 13);

      range = new IntegerRange(3, 7, false, true);
      verify(range, new int[]{-4}, new bool[]{false}, new int[]{10}, new bool[]{true}, "(-4-10]", 14);

      range = new IntegerRange(3, 7, true, false);
      verify(range, new int[]{-4}, new bool[]{true}, new int[]{10}, new bool[]{false}, "[-4-10)", 14);

      range = new IntegerRange("[3^7)");
      verify(range, new int[]{-4}, new bool[]{true}, new int[]{10}, new bool[]{false}, "[-4-10)", 14);

      range = new IntegerRange(" [ 3 ^ 7 ) ");
      verify(range, new int[]{-4}, new bool[]{true}, new int[]{10}, new bool[]{false}, "[-4-10)", 14);
    }

    [TestCase]
    public void TestMerge1() {
      IntegerRange range = null;

      range = new IntegerRange("5, 6");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{6}, new bool[]{true}, "5-6", 2);

      range = new IntegerRange("6, 5");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{6}, new bool[]{true}, "5-6", 2);

      range = new IntegerRange("5-7,8,9-11");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{11}, new bool[]{true}, "5-11", 7);

      range = new IntegerRange("[5-7),8,(9-11]");
      verify(range,
             new int[]{5, 8, 9}, new bool[]{true, true, false},
             new int[]{7, 8, 11}, new bool[]{false, true, true},
             "[5-7),8,(9-11]", 5);
    }

    [TestCase]
    public void TestUnbounded() {
      IntegerRange range = null;

      range = new IntegerRange("(5-)");
      verify(range, new int[]{5}, new bool[]{false}, new int[]{int.MaxValue}, new bool[]{true}, "(5-", null);

      range = new IntegerRange("(-5)");
      verify(range, new int[]{-5}, new bool[]{true}, new int[]{-5}, new bool[]{true}, "-5", 1);

      range = new IntegerRange("-5");
      verify(range, new int[]{-5}, new bool[]{true}, new int[]{-5}, new bool[]{true}, "-5", 1);

      range = new IntegerRange("-(-5)");
      verify(range, new int[]{int.MinValue}, new bool[]{true}, new int[]{-5}, new bool[]{false}, "-(-5)", null);

      range = new IntegerRange("-(5)");
      verify(range, new int[]{int.MinValue}, new bool[]{true}, new int[]{5}, new bool[]{false}, "-(5)", null);

      range = new IntegerRange("-");
      verify(range, new int[]{int.MinValue}, new bool[]{true}, new int[]{int.MaxValue}, new bool[]{true}, "-", null);

      range = new IntegerRange("(5-");
      verify(range, new int[]{5}, new bool[]{false}, new int[]{int.MaxValue}, new bool[]{true}, "(5-", null);

      range = new IntegerRange("5-");
      verify(range, new int[]{5}, new bool[]{true}, new int[]{int.MaxValue}, new bool[]{true}, "5-", null);

      range = new IntegerRange("-5-");
      verify(range, new int[]{-5}, new bool[]{true}, new int[]{int.MaxValue}, new bool[]{true}, "-5-", null);

      range = new IntegerRange("--5");
      verify(range, new int[]{int.MinValue}, new bool[]{true}, new int[]{-5}, new bool[]{true}, "-(-5]", null);

      range = new IntegerRange("--5)");
      verify(range, new int[]{int.MinValue}, new bool[]{true}, new int[]{-5}, new bool[]{false}, "-(-5)", null);
    }

    [TestCase]
    public void TestAmbiguous() {
      IntegerRange range = null;

      range = new IntegerRange("-5-3");
      verify(range, new int[]{-5}, new bool[]{true}, new int[]{3}, new bool[]{true}, "-5-3", 9);

      range = new IntegerRange("-5--3");
      verify(range, new int[]{-5}, new bool[]{true}, new int[]{-3}, new bool[]{true}, "-5--3", 3);
    }
 
    [TestCase]
    public void TestDontAddNonContiguousDuplicate() {
      IntegerRange range = new IntegerRange(1947);
      range.Add(13, false);
      range.Add(1947, false);

      Assert.AreEqual("13,1947", range.ToString());
    }
  }
}
